#region Imported Namespaces
using System;
using System.Drawing;
using System.Windows.Forms;
using System.Runtime.InteropServices;
#endregion

namespace PHSRAG.Utility
{
	#region Class CenterDialog
	/// <summary>
	/// The CenterDialog is used to center child dialog boxes over their parent Windows Form.
	/// </summary>
	/// <remarks>
	/// Why do we need this? After all, all Windows in .NET support the property to automatically do this centering.
	/// The problem is that it does not apply to dialog boxes that are part of the Windows common controls suite.
	/// For example, a message box. The CenterDilaog class manages the actual centering of ALL child dialog boxes for
	/// a parent.
	/// </remarks>
	/// <example>
	/// Here is an example of how the CenterDialog could be used.
	/// <code>
	/// <![CDATA[
	/// ...
	/// CenterDialog dialog = new CenterDialog(this);
	/// dialog.BeginCentering();
	/// MessageBox.Show(this, "This dialog box will be centered over its parent");
	///	dialog.EndCentering();
	/// ...
	/// ]]>
	/// </code>
	/// </example>
	public class CenterDialog
	{
		#region Private Types
		private enum WindowMessage : int
		{
			WMINITDIALOG = 0x0110
		}

		private enum WindowFlag : int
		{
			SWPNOSIZE = 1,
			SWPNOZORDER = 4
		}

		[StructLayout(LayoutKind.Sequential)]
		private struct CWPRETSTRUCT
		{
			public int		result;
			public IntPtr	lParam;
			public IntPtr	wParam;
			public int		message;
			public IntPtr	handle;
		}
		#endregion

		#region Interop Definitions
		[DllImport("user32.dll")]
		private static extern int GetClassName(IntPtr wnd, System.Text.StringBuilder sb, int count);

		[DllImport("user32.dll")]
		private static extern IntPtr GetParent(IntPtr wnd);

		[DllImport("user32.dll")]
		private static extern int GetWindowRect(IntPtr wnd, out Rectangle rect);

		[DllImport("user32.dll")]
		private static extern bool SetWindowPos(IntPtr wnd, IntPtr wndAfter, int x, int y, int cx, int cy, int flags);
		#endregion

		#region Instance Variables
		private WindowsHooker				hooker;
		private WindowsHooker.HookProcedure	hookProc;
		private Control						parent;
		#endregion

		#region Constructors
		/// <summary>
		/// Construct an instance of the CenterDialog type.
		/// </summary>
		/// <param name="parent">Reference to a contol over which the centering action will take place</param>
		public CenterDialog(Control parent)
		{
			hooker	= new WindowsHooker();
			hookProc = new WindowsHooker.HookProcedure(HookProc);
			this.parent = parent;
		}
		#endregion

		#region Public Methods
		/// <summary>
		/// This method enables the centering operation of all child dialog boxes for the parent till such time its
		/// counterpart, EndCentering() is called.
		/// </summary>
		public void BeginCentering()
		{
			hooker.Hook(hookProc, WindowsHooker.HookType.WHCALLWNDPROCRET);
		}

		/// <summary>
		/// This method ends the centering of all child dialog boxes.
		/// </summary>
		public void EndCentering()
		{
			hooker.Unhook();
		}
		#endregion

		#region Private Methods
		/// <summary>
		/// Helper method to center a window over the parent control (specified while constructing this object).
		/// Win32 dialog boxes have a class name "#32770" (this has been the case from the earliest days of Windows).
		/// Whenever any window of that class gets created, its position will be set such that it is centered over
		/// the control specified while construting this object.
		/// </summary>
		/// <param name="wnd"></param>
		private void CenterWindow(IntPtr wnd)
		{
			System.Text.StringBuilder sb = new System.Text.StringBuilder(256);
			GetClassName(wnd, sb, 256);
			string className = sb.ToString();

			if (className == "#32770")
			{
				Rectangle parentRect = parent.Bounds;
														
				Rectangle rect = Rectangle.Empty;
				GetWindowRect(wnd, out rect);
				rect.Width -= rect.X;
				rect.Height -= rect.Y;
														
				int x = parentRect.Left + ((parentRect.Width - rect.Width) / 2);
				int y = parentRect.Top + ((parentRect.Height - rect.Height) / 2);
											
				SetWindowPos(wnd, (IntPtr)0, x, y, 0, 0, (int)WindowFlag.SWPNOSIZE | (int)WindowFlag.SWPNOZORDER);
			}
		}

		/// <summary>
		/// Callback method invoked by the WindowsHooker. If the reason for invocation is that a dialog window is being
		/// initialized and if that window is a proper child of the control over which centering has to occur, then and
		/// only then will the dialog be centered.
		/// </summary>
		/// <param name="code">Hook code passed to the current hook procedure</param>
		/// <param name="wParam">wparam passed to the current hook procedure</param>
		/// <param name="lParam">lparam passed to the current hook procedure</param>
		/// <returns></returns>
		private int HookProc(int code, IntPtr wParam, IntPtr lParam)
		{
			try
			{
				CWPRETSTRUCT c = (CWPRETSTRUCT)Marshal.PtrToStructure(lParam, typeof(CWPRETSTRUCT));
				if ((c.message == (int)WindowMessage.WMINITDIALOG) && (GetParent(c.handle) == parent.Handle))
					CenterWindow(c.handle);
			}
			catch (Exception e)
			{
				System.Diagnostics.Trace.WriteLine(e.Message);
			}
			return 0;
		}
		#endregion
	}
	#endregion

	#region Class WindowsHooker
	/// <summary>
	/// The WindowsHooker (pardon the pun) is a helper class which allowing "hooking" into the Win32 message pump.
	/// This class is used by the CenterWindow class to know when a dialog window is being created.
	/// </summary>
	internal class WindowsHooker
	{
		#region Public Types
		public delegate int HookProcedure(int code, IntPtr wParam, IntPtr lParam);

		public enum HookType : int
		{
			FirstValue			= 0,
			WHJOURNALRECORD		= 0,
			WHJOURNALPLAYBACK	= 1,
			WHKEYBOARD			= 2,
			WHGETMESSAGE		= 3,
			WHCALLWNDPROC		= 4,
			WHCBT				= 5,
			WHSYSMSGFILTER		= 6,
			WHMOUSE				= 7,
			WHHARDWARE			= 8,
			WHDEBUG				= 9,
			WHSHELL				= 10,
			WHFOREGROUNDIDLE	= 11,
			WHCALLWNDPROCRET	= 12,
			WHKEYBOARDLL		= 13,
			WHMOUSELL			= 14,
			LastValue			= 14
		}
		#endregion

		#region Interop Definitions
		[DllImport("user32.dll")]
		private static extern IntPtr SetWindowsHookEx(int type, HookProcedure proc, IntPtr instance, int threadID);

		[DllImport("user32.dll")]
		private static extern int UnhookWindowsHookEx(IntPtr hook);

		[DllImport("user32.dll")]
		private static extern int CallNextHookEx(IntPtr hook, int code, IntPtr wParam, IntPtr lParam);
		#endregion

		#region Instance Variables
		private IntPtr			hook = IntPtr.Zero;
		private HookProcedure	hooker;
		private HookProcedure	hookCallback = null;
		#endregion

		#region Constructors
		public WindowsHooker()
		{
			hooker = new HookProcedure(Hooker);
		}
		#endregion

		#region Public Methods
		public void Hook(HookProcedure proc, HookType type)
		{
			if (hook == IntPtr.Zero)
			{
				hookCallback = proc;
				hook = SetWindowsHookEx((int)type, hooker, IntPtr.Zero, System.Threading.Thread.CurrentThread.ManagedThreadId);
			}
		}

		public void Unhook()
		{
			if (hook != IntPtr.Zero)
			{
				UnhookWindowsHookEx(hook);
				hook = IntPtr.Zero;
			}
		}
		#endregion

		#region Private Methods
		private int Hooker(int code, IntPtr wParam, IntPtr lParam)
		{
			if ((code >= 0) && (hookCallback != null))
				hookCallback(code, wParam, lParam);
			return CallNextHookEx(hook, code, wParam, lParam);
		}
		#endregion
	}
	#endregion
}

// --- EOF ---
